use moss::util::type_add_colon2;
use moss::{from_attrs::FromAttrs, from_meta::FromMeta};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{DeriveInput, Ident};
use syn::{Expr, Type};
use syn::{Fields, Result};

#[derive(FromMeta)]
#[name("validate")]
pub enum ValidateAttr {
    Length(Expr, Expr, Option<String>),
    Range(Expr, Expr, Option<String>),
    Constant(Expr, Option<String>),
    Regex(String, Option<String>),
    Enumer(Type, Option<String>),

    #[strip]
    SelfValidate(Option<String>),

    #[strip]
    OnlySelfValidate,
}

#[derive(FromAttrs)]
pub struct FieldAttrs {
    pub validates: Vec<ValidateAttr>,
    pub name: Option<NameAttr>,
}

#[derive(FromMeta)]
#[name("name")]
pub struct NameAttr(pub Option<String>);

pub fn visit_fields(fields: &syn::Fields, variant: Option<&Ident>) -> Result<TokenStream> {
    let mut generated = quote!();
    let mut variant_member = quote!();
    for (index, field) in fields.iter().enumerate() {
        let field_attrs = FieldAttrs::try_from(&field.attrs)?;
        let validates = field_attrs.validates;
        let mut ident_token = quote!(None);
        if let Some(ident) = &field.ident {
            let ident_name = ident.to_string();
            ident_token = quote!(Some(#ident_name));
        }
        let mut name_token = quote!(None);
        if let Some(NameAttr(Some(message))) = field_attrs.name {
            name_token = quote!(Some(#message));
        }
        let data_token;
        let mut message_token = quote!(e);
        let mut field_ty = field.ty.clone();

        if variant.is_none() {
            match &field.ident {
                Some(ident) => {
                    data_token = quote!(&self.#ident);
                }
                None => {
                    let index = syn::Index::from(index);
                    data_token = quote!(&self.#index);
                }
            };
        } else {
            match &field.ident {
                Some(ident) => {
                    data_token = quote!(#ident);
                    variant_member = quote! {#variant_member #ident,};
                }
                None => {
                    let ident = format_ident!("_unname_{index}");
                    data_token = quote!(#ident);
                    variant_member = quote! {#variant_member #ident,};
                }
            };
        }
        type_add_colon2(&mut field_ty);
        for validate in validates {
            match validate {
                ValidateAttr::Length(min, max, message) => {
                    if let Some(message) = message {
                        message_token = quote!(format!(#message));
                    }
                    generated = quote! {
                        #generated
                        let result = LengthValidator::<#min, #max>.validate(#ident_token, #name_token, #data_token);
                    };
                }
                ValidateAttr::Range(min, max, message) => {
                    if let Some(message) = message {
                        message_token = quote!(format!(#message));
                    }
                    generated = quote! {
                        #generated
                        let result = RangeValidator { min: #min, max: #max }.validate(#ident_token, #name_token, #data_token);
                    };
                }
                ValidateAttr::Regex(regex, message) => {
                    if let Some(message) = message {
                        message_token = quote!(format!(#message));
                    }
                    generated = quote! {
                        #generated
                        let result = RegexValidator { regex: lazy_regex::regex!(#regex),}.validate(#ident_token, #name_token, #data_token);
                    };
                }
                ValidateAttr::Enumer(enumer_ty, message) => {
                    if let Some(message) = message {
                        message_token = quote!(format!(#message));
                    }
                    generated = quote! {
                        #generated
                        let result = EnumerValidator::<#enumer_ty>::default().validate(#ident_token, #name_token, #data_token);
                    };
                }
                ValidateAttr::SelfValidate(message) => {
                    if let Some(message) = message {
                        message_token = quote!(format!(#message));
                    }
                    generated = quote! {
                        #generated
                        let result = <#field_ty as validator::SelfValidator>::validate(#data_token, #ident_token, #name_token,);
                    };
                }
                ValidateAttr::OnlySelfValidate => {
                    generated = quote! {
                        #generated
                        let result = <#field_ty as validator::SelfValidator>::validate(#data_token, #ident_token, #name_token,);
                    };
                }
                ValidateAttr::Constant(value, message) => {
                    if let Some(message) = message {
                        message_token = quote!(format!(#message));
                    }
                    generated = quote! {
                        #generated
                        let result = ConstantValidator { value: #value.into() }.validate(#ident_token, #name_token, #data_token);
                    };
                }
            }
            generated = quote! {
                #generated

                if let Err(e) = result {
                    let mut field = ident.unwrap_or_default();
                    if let Some(name) = name {
                        if !name.is_empty() {
                            field = name;
                        }
                    }
                    return Err(format!("{}{}", field, #message_token));
                }
            };
        }
    }
    generated = quote! {
        #generated
        Ok(())
    };
    if let Some(variant) = variant {
        match fields {
            Fields::Named(_) => {
                variant_member = quote!({#variant_member});
            }
            Fields::Unnamed(_) => {
                variant_member = quote! {(#variant_member)};
            }
            Fields::Unit => {}
        }
        generated = quote! {
            Self::#variant #variant_member => {
                #generated
            }
        };
    }

    Ok(generated)
}

pub fn expand(derive_input: &DeriveInput) -> Result<TokenStream> {
    let for_ident = &derive_input.ident;
    let (impl_generics, ty_generics, where_clause) = derive_input.generics.split_for_impl();

    let mut generated = quote!();
    match &derive_input.data {
        syn::Data::Struct(data_struct) => {
            let fields_generated = visit_fields(&data_struct.fields, None)?;
            generated = quote! {
                #generated
                #fields_generated
            };
        }
        syn::Data::Enum(data_enum) => {
            for variant in &data_enum.variants {
                let fields_generated = visit_fields(&variant.fields, Some(&variant.ident))?;
                generated = quote! {
                    #generated
                    #fields_generated
                };
            }
            generated = quote! {
                match self {
                    #generated
                }
            }
        }
        syn::Data::Union(_) => return Err(moss::error::Error::unexpected_type("union").into()),
    }
    let generated = quote! {
        impl #impl_generics validator::SelfValidator for #for_ident #ty_generics #where_clause {
            type Error = String;
            fn validate(&self, ident: Option<&str>, name: Option<&str>) -> std::result::Result<(), Self::Error> {
                #generated
            }
        }
    };
    Ok(generated)
}
